/**
 * Copyright 2011 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.google.solarchallenge.client.event;

import static com.google.solarchallenge.client.rpc.ServiceProvider.getServiceProvider;
import static com.google.solarchallenge.shared.Utils.RedirectionUtils.generateValidParamsToAttach;

import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.user.client.History;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.RootLayoutPanel;
import com.google.solarchallenge.client.gwtui.admin.adminlinks.AdminUiPresenter;
import com.google.solarchallenge.client.gwtui.admin.adminlinks.AdminUiView;
import com.google.solarchallenge.client.gwtui.admin.edituseraccountdetails.EditUserAccountDetailsPresenter;
import com.google.solarchallenge.client.gwtui.admin.edituseraccountdetails.EditUserAccountDetailsView;
import com.google.solarchallenge.client.gwtui.applicants.applicanthome.ApplicantHomePresenter;
import com.google.solarchallenge.client.gwtui.applicants.applicanthome.ApplicantHomeView;
import com.google.solarchallenge.client.gwtui.applicants.applicationinprogress.ApplicationInProgressPresenter;
import com.google.solarchallenge.client.gwtui.applicants.applicationinprogress.ApplicationInProgressView;
import com.google.solarchallenge.client.gwtui.applicants.createapplication.CreateApplicationPresenter;
import com.google.solarchallenge.client.gwtui.applicants.createapplication.CreateApplicationView;
import com.google.solarchallenge.client.gwtui.applicants.manageapplications.ManageApplicationsPresenter;
import com.google.solarchallenge.client.gwtui.applicants.manageapplications.ManageApplicationsView;
import com.google.solarchallenge.client.gwtui.applicants.register.agreement.ApplicantRegisterAgreementPresenter;
import com.google.solarchallenge.client.gwtui.applicants.register.agreement.ApplicantRegisterAgreementView;
import com.google.solarchallenge.client.gwtui.applicants.register.signup.ApplicantRegisterPresenter;
import com.google.solarchallenge.client.gwtui.applicants.register.signup.ApplicantRegisterView;
import com.google.solarchallenge.client.gwtui.login.LoginPresenter;
import com.google.solarchallenge.client.gwtui.login.LoginView;
import com.google.solarchallenge.client.gwtui.mainpanel.MainPanelPresenter;
import com.google.solarchallenge.client.gwtui.mainpanel.MainPanelView;
import com.google.solarchallenge.shared.ApplicantStatus;
import com.google.solarchallenge.shared.LoginCategory;
import com.google.solarchallenge.shared.ServletPaths;
import com.google.solarchallenge.shared.ValidParams;
import com.google.solarchallenge.shared.dtos.UserAccountDto;
import com.google.solarchallenge.shared.exceptions.InvalidURLParamsException;

/**
 * History Handler for Solar Challenge App.
 * This is also the backbone for controlling the UI presented to the user.
 *
 * @author Arjun Satyapal
 */
public class HistoryHandler implements ValueChangeHandler<String> {
  private static HistoryHandler instance = new HistoryHandler();

  public static HistoryHandler getInstance() {
    return instance;
  }

  // Private constructor to force singleton.
  private HistoryHandler() {
  }

  /*
   * Since history tokens can have parameters, so not using EventCategory enum
   * directly.
   */
  public static void handleNewToken(String newToken) {
    String historyToken = History.getToken();

    if (historyToken.isEmpty()) {
      newToken = EventCategory.HOME.getToken();
      History.newItem(newToken);
      History.fireCurrentHistoryState();
      return;
    }

    if (newToken.equals(historyToken)) {
      History.fireCurrentHistoryState();
    } else {
      History.newItem(newToken);
    }
  }

  @Override
  public void onValueChange(final ValueChangeEvent<String> event) {
    EventCategory historyEventCategory = null;

    try {
      historyEventCategory = EventCategory.getHistoryEventCategoryByToken(event
          .getValue());
    } catch (InvalidURLParamsException e) {
      // TODO : For better experience, developer can do auto-redirect here.
      Window.alert("Invalid URL. Redirecting to homepage..");
      historyEventCategory = EventCategory.HOME;
    }

    /*
     * If Category requires login, then validate that user is logged in. If not,
     * then force user to login with currentToken as clientCallbackToken. else
     * proceed.
     */
    if (historyEventCategory.requiresLogin()) {
      forceLoginIfNotAlreadyLoggedIn(historyEventCategory, event.getValue());
    } else {
      // User does not require login.
      handleEventCategory(historyEventCategory, event.getValue(), null /* userAccountDto */);
    }
  }

  private void forceLoginIfNotAlreadyLoggedIn(
      final EventCategory historyEventCategory, final String historyToken) {
    AsyncCallback<UserAccountDto> callback = new AsyncCallback<UserAccountDto>() {
      @Override
      public void onFailure(Throwable caught) {
        // Redirect user to Login Screen with current even as callbackToken.
        handleNewToken(createTokenWithParams(
            EventCategory.LOGIN_UI,
            generateValidParamsToAttach(ValidParams.CLIENT_CALLBACK_TOKEN,
                historyToken)));
      }

      @Override
      public void onSuccess(UserAccountDto userAccountDto) {
        // Successfully got the result for UserAccountDto.
        if (userAccountDto == null) {
          // User is not logged in.
          // TODO : Find better way to handle this as user has not
          // logged in.
          onFailure(null);
        } else {
          // TODO : Validate that current category is correct.
          // e.g. for Admin only pages, see that user is Admin.
          // Also server should validate the user Category as client side
          // validation cannot be trusted. Leaving that for future developers.
          handleEventCategory(historyEventCategory, historyToken,
              userAccountDto);
        }
      }
    };
    getServiceProvider().getLoginService().getLoggedInUserDTO(callback);
  }

  public void handleEventCategory(EventCategory historyEventCategory,
      final String historyToken, final UserAccountDto userAccountDtO) {
    LoginCategory currUserLoginCategory = null;
    if (userAccountDtO == null) {
      currUserLoginCategory = LoginCategory.GUEST;
    } else {
      currUserLoginCategory = userAccountDtO.getLoginCategory();
    }

    if (historyEventCategory.isUserAllowed(currUserLoginCategory)) {
      switch (historyEventCategory) {
        case HOME:
          MainPanelPresenter mainPanelPresenter = new MainPanelPresenter(
              new MainPanelView(), historyToken);
          mainPanelPresenter.go(RootLayoutPanel.get());
          break;

        case LOGIN_UI:
          LoginPresenter loginPresenter = new LoginPresenter(new LoginView(),
              historyToken);
          loginPresenter.go(RootLayoutPanel.get());
          break;

        case ADMIN_UI:
          AdminUiPresenter adminUiPresenter = new AdminUiPresenter(
              new AdminUiView(), historyToken);
          adminUiPresenter.go(RootLayoutPanel.get());
          break;

        case EDIT_USER_ACCOUNT_DETAILS:
          EditUserAccountDetailsPresenter approverShowEditPresenter
              = new EditUserAccountDetailsPresenter(
                  new EditUserAccountDetailsView(), historyToken);
          approverShowEditPresenter.go(RootLayoutPanel.get());
          break;

        case APPLICANT_REGISTER:
          ApplicantRegisterPresenter applicantRegisterPresenter
              = new ApplicantRegisterPresenter(
                  new ApplicantRegisterView(), historyToken);
          applicantRegisterPresenter.go(RootLayoutPanel.get());
          break;

        case APPLICANT_HOME:
        case APPLICANT_AGREEMENT:
          getServiceProvider().getApplicantService().getApplicantStatus(
              userAccountDtO.getKey(),
              getCallbackForApplicantHome(historyEventCategory));
          break;

        case CREATE_APPLICATION:
          CreateApplicationPresenter createApplicationPresenter
              = new CreateApplicationPresenter(
                  new CreateApplicationView(), historyToken);
          createApplicationPresenter.go(RootLayoutPanel.get());
          break;

        case APPLICANT_NEW_APPLICATION:
          // TODO : This can get bypassed even if applicant is
          // not verified. Future developers should improve on this.
          // For demo purpose, ignoring that.
          ApplicationInProgressPresenter newApplicationPresenter
              = new ApplicationInProgressPresenter(
                  new ApplicationInProgressView(), historyToken);
          newApplicationPresenter.go(RootLayoutPanel.get());
          break;

        case MANAGE_APPLICATIONS:
          ManageApplicationsPresenter manageApplicationsPresenter
              = new ManageApplicationsPresenter(
                  new ManageApplicationsView(), historyToken);
          manageApplicationsPresenter.go(RootLayoutPanel.get());
          break;

        default:
          Window.alert("Invalid event : " + historyEventCategory
              + ". Redirecting to home.");
          handleNewToken(EventCategory.HOME.getToken());
          break;
      }
    } else {
      // User is not authorized. So redirecting to error page.
      // TODO : Add more error handling here as required.
      Window.Location.assign(ServletPaths.NOT_AUTHORIZED.getRelativePath());
    }
  }

  private AsyncCallback<ApplicantStatus> getCallbackForApplicantHome(
      final EventCategory historyEventCategory) {
    AsyncCallback<ApplicantStatus> callback
      = new AsyncCallback<ApplicantStatus>() {
      @Override
      public void onFailure(Throwable caught) {
        Window.alert("Failed to get ApplicantStatus due to reason : " + caught
            .getLocalizedMessage());
      }

      @Override
      public void onSuccess(ApplicantStatus result) {
        handleApplicantHomeEvent(historyEventCategory, result);
      }
    };

    return callback;
  }


  private void handleApplicantHomeEvent(EventCategory historyEventCategory,
      ApplicantStatus applicantStatus) {
    switch (applicantStatus) {
      case AGREEMENT_DENIED:
        Window.alert("In order to access website, you have to accept the "
            + "agreement. Redirecting to Agreement page.");
        //$FALL-THROUGH$
      case PENDING_AGREEMENT:
        // TODO : May be history can be handled better but punting on
        // that for the moment.
        ApplicantRegisterAgreementPresenter applicantRegisterAgreementPresenter
            = new ApplicantRegisterAgreementPresenter(
                new ApplicantRegisterAgreementView(), null /* historyToken */);
        applicantRegisterAgreementPresenter.go(RootLayoutPanel.get());
        break;

      case PENDING_VERIFICATION:
        Window.alert("Your Registration is Pending Verification. Once verifed,"
            + " you will be granted " + "access. Redirecting to Home screen.");
        handleNewToken(EventCategory.HOME.getToken());
        break;

      case VERIFIED:
        ApplicantHomePresenter applicantHomePresenter
            = new ApplicantHomePresenter(
                new ApplicantHomeView(), null);
        applicantHomePresenter.go(RootLayoutPanel.get());
        break;

      default:
        Window.alert("Invalid ApplicantStatus : " + applicantStatus
            + ". Redirecting to home.");
        handleNewToken(EventCategory.HOME.getToken());
    }
  }

  public static String createTokenWithParams(EventCategory eventCategory,
      String params) {
    return eventCategory.getToken() + "?" + params;
  }
}
